#!/usr/bin/python
"""
util.py
"""

__all__ = [
    'get_actions',  # some people might want this
    'get_rules_and_actions',
    'get_value',
    ]

import inspect
import sys


class Error(RuntimeError):
  pass


def _GetObjects(m, pred):
  """
  Filters objects defined in a module by a predicate.
  """
  if isinstance(m, basestring):
    try:
      m = sys.modules[m]
    except KeyError:
      # Is this catchable?  Use a package-level exception?
      raise Error("Couldn't find module %r" % m)

  objs = []
  for name in dir(m):
    obj = getattr(m, name)
    if pred(name, obj):
      objs.append(obj)
  return objs


def get_value(m, obj_type, name):
  def Pred(n, obj):
    return isinstance(obj, obj_type) and n == name

  obj_list = _GetObjects(m, Pred)
  if obj_list > 1:
    return obj_list[0]
  else:
    raise Error("Couldn't find object of type %s named %r in %r" %
        (obj_type, name, m))


def _get_actions(m, arg_name=None):
  """
  Args:
    m: A module or a string

  Returns:
    Functions in the module which have a 'parsed' argument, e.g.:

    def f(parsed):
      ...

  """
  if arg_name:
    arg_list = [arg_name]
  else:
    arg_list = ['parsed']

  def IsAction(name, obj):
    if not callable(obj):
      return False
    try:
      a = inspect.getargspec(obj)
    except TypeError:
      return False  # classes are callable but have no argspec

    # NOTE: In Python 2.5, the result of getargspec is positional.  In Python
    # 2.6, you can use names.
    args, _, _, _ = a
    if args == arg_list:
      return True

  return _GetObjects(m, IsAction)


def get_actions(m, arg_name=None):
  """
  Returns:
    Dictionary of { name -> function }
  """
  func_list = _get_actions(m, arg_name=arg_name)
  return dict((f.__name__, f) for f in func_list)


def get_rules_and_actions(m, arg_name=None):
  """Get rules and actions."""
  func_list = _get_actions(m, arg_name=arg_name)
  # Join all docstrings.
  rules = []
  for f in func_list:
    doc = f.__doc__
    if doc:
      rules.append(doc)

  # newline makes it easier to read, will not affect definitions.
  rules = '\n'.join(rules)
  actions = dict((f.__name__, f) for f in func_list)

  return rules, actions
